/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

#include "simodo/variable/json/Serialization.h"
#include "simodo/variable/json/parser/JsonRdp.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/token/FileStream.h"
#include "simodo/inout/token/RefBufferStream.h"
#include "simodo/variable/json/Rpc.h"

#include <fstream>
#include <sstream>

namespace simodo::variable
{
    namespace
    {
        class StringReporter: public inout::Reporter_abstract
        {
            std::string _message;

        public:
            virtual void report(const inout::SeverityLevel level,
                                const inout::Location & ,
                                const std::string & briefly,
                                const std::string & atlarge) override
            {
                _message += inout::getSeverityLevelName(level) + briefly;
                if (!atlarge.empty())
                    _message += " (" + atlarge + ")\n";
            }

            std::string message() const { return _message; }
        };

        void printJson(const Value & value, std::ostream & out, int level, bool compress, bool skip_empty_parameters)
        {
            switch (value.type())
            {
            case ValueType::Bool:
                out << (value.getBool() ? "true" : "false");
                break;
            case ValueType::Int:
                out << value.getInt();
                break;
            case ValueType::Float:
                out << value.getReal(); // clearNumberFractionalPart
                break;
            case ValueType::String:
                {
                    std::u16string str = value.getString();
                    inout::replaceAll(str,u"\\",u"\\\\");
                    inout::replaceAll(str,u"\"",u"\\\"");
                    out << "\"" << inout::toU8(str) << "\"";
                }
                break;
            case ValueType::Null:
            // case ValueType::Undefined:
                out << "null";
                break;
            case ValueType::Object:
                {
                    const std::shared_ptr<Object> object = value.getObject();

                    out << "{";

                    if (!compress)
                        out << std::endl;

                    int i = 0;

                    for(const Variable & var : object->variables()) {
                        // if (var.type() == ValueType::Null)
                        //     continue;
                        if (level <= 1 && skip_empty_parameters && var.type() == ValueType::Null)
                            continue;

                        if (i++ > 0) {
                            out << ",";
                            if (!compress)
                                out << std::endl;
                        }

                        if (!compress)
                            for(int i=0; i < level; ++i)
                                out << "\t";

                        if (!compress)
                            out << "\"" << inout::toU8(var.name()) << "\" : ";
                        else
                            out << "\"" << inout::toU8(var.name()) << "\":";

                        printJson(var.value(), out, level+1, compress, skip_empty_parameters);
                    }

                    if (!compress)
                        out << std::endl;

                    if (!compress) // -V581
                        for(int i=0; i < level-1; ++i)
                            out << "\t";

                    out << "}";
                }
                break;
            case ValueType::Array:
                {
                    std::shared_ptr<Array> array = value.getArray();

                    out << "[";

                    if (!compress)
                        out << std::endl;

                    int i = 0;

                    for(const Value & v : array->values())
                    {
                        if (i++ > 0) {
                            out << ",";
                            if (!compress)
                                out << std::endl;
                        }

                        if (!compress)
                            for(int i=0; i < level; ++i)
                                out << "\t";

                        printJson(v, out, level+1, compress, skip_empty_parameters);
                    }

                    if (!compress)
                        out << std::endl;

                    if (!compress) // -V581
                        for(int i=0; i < level-1; ++i)
                            out << "\t";

                    out << "]";
                }
                break;
            default:
                out << "\"unrecognizable type of value: " + getValueTypeName(value.type())+ "\"";
                break;
            }
        }
    }

    std::string loadJson(const std::string &file_name, Value &value)
    {
        StringReporter  reporter;
        JsonRdp         parser(reporter, file_name, value);
        std::string     result;

        bool ok = parser.parse();

        if (!ok)
            result = reporter.message();

        return result;
    }

    std::string loadJson(std::istream &in, Value &value, const std::string &file_name)
    {
        inout::InputStream  stream(in);
        StringReporter      reporter;
        JsonRdp             parser(reporter, file_name, value);
        std::string         result;

        bool ok = parser.parse(stream);

        if (!ok)
            result = reporter.message();

        return result;
    }

    bool saveJson(const Value &value, std::ostream &out, bool compress, bool skip_empty_parameters)
    {
        if (out.fail())
            return false;

        printJson(value, out, 1, compress, skip_empty_parameters);

        out.flush();

        return out.good();
    }

    bool saveJson(const Value &value, const std::string &file_name, bool compress, bool skip_empty_parameters)
    {
        std::ofstream   out(file_name);

        if (!out)
            return false;

        return saveJson(value, out, compress, skip_empty_parameters);
    }

    bool saveJson(const JsonRpc & rpc, std::ostream & out, bool compress, bool skip_empty_parameters)
    {
        return saveJson(rpc.value(), out, compress, skip_empty_parameters);
    }

    Value fromJson(const std::string & json_string)
    {
        std::istringstream  is(json_string);
        inout::InputStream  input(is);
        Value               res;
        StringReporter      reporter;
        JsonRdp             parser(reporter,{},res);

        parser.parse(input);

        return res;
    }

    Value fromJson(const std::u16string & json_string)
    {
        return fromJson(inout::toU8(json_string));
    }

    std::string toJson(const Value & value, bool compress, bool skip_empty_parameters)
    {
        std::ostringstream os;
        saveJson(value, os, compress, skip_empty_parameters);
        return os.str();
    }

}